library(ggplot2)
library(dplyr)
library(tidyr)
library("ggpubr")
library(LDATS)
library(ggVennDiagram)
library(stringr)
library(abind)
library(patchwork)

source("utils.R")

Visualizations on three sets of networks. First set consists of networks trained in 30 replications on CIFAR 10 training set with 500 sample validation set extracted randomly in each replication. Second set consists of networks trained in a single replication on half of CIFAR 10 training set. Third set are networks trained in 10 replications on half of CIFAR 100 training set.

CIFAR 10

ens_pw_cal[, c("class1", "class2", "bin_c", "combining_method")] <- lapply(ens_pw_cal[, c("class1", "class2", "bin_c", "combining_method")], as.factor)
Error in `[.data.frame`(ens_pw_cal, , c("class1", "class2", "bin_c", "combining_method")) : 
  undefined columns selected
for (cl1 in 1:(classes - 1))
{
  for (cl2 in (cl1 + 1):classes)
  {
    combiner_plt <- combiner_coefs %>% filter(class1 == cl1 & class2 == cl2) %>% ggplot() + geom_boxplot(aes(x=coefficient, y=value)) +
      facet_grid(cols=vars(train_type), rows=vars(combining_method)) + ggtitle("combiner coefficients")
    
    acc_plt_net <- net_pw_results %>% filter(class1 == (cl1 - 1) & class2 == (cl2 - 1)) %>% ggplot(mapping=aes(x=network, y=accuracy)) + 
      geom_boxplot() + ggtitle("Pairwise accuracies of networks")
    
    acc_plt_ens <- ens_pw_results %>% filter(class1 == (cl1 - 1) & class2 == (cl2 - 1)) %>% ggplot(mapping=aes(y=accuracy)) +
      geom_boxplot() + facet_grid(cols=vars(train_set), rows=vars(combining_method)) + ggtitle("Pairwise accuracies of combiner output") +
      theme(axis.ticks.x=element_blank(), axis.text.x=element_blank())
    
    
    print((acc_plt_net/combiner_plt/acc_plt_ens) + plot_annotation(title=paste("Classes ", cl1, " vs ", cl2)))
  }
}

Val training accuracies have higher variance.

An attempt to visualize calibration of LDAs. Almost all elements are present near the zero and one probabilities, therefore the visualization is not very informative.

for (cl1 in 1:(classes - 1))
{
  for (cl2 in (cl1 + 1):classes)
  {
    cal_count_plt <- ens_pw_cal %>% filter(class1 == (cl1 - 1) & class2 == (cl2 - 1)) %>% ggplot() +
      geom_boxplot(mapping=aes(x=bin_c, y=bin_count)) + facet_grid(cols=vars(train_set), rows=vars(combining_method)) +
      ggtitle(paste("Classes ", cl1, " vs ", cl2)) + xlab("Confidence") + ylab("Count")
    
    print(cal_count_plt)
  }
}

for (cl1 in 1:(classes - 1))
{
  for (cl2 in (cl1 + 1):classes)
  {
    cal_plt <- ens_pw_cal %>% filter(class1 == (cl1 - 1) & class2 == (cl2 - 1)) %>% ggplot() +
      geom_boxplot(mapping=aes(x=bin_c, y=bin_accuracy)) + facet_grid(cols=vars(train_set), rows=vars(combining_method)) +
      ggtitle(paste("Classes ", cl1, " vs ", cl2)) + xlab("Confidence") + ylab("Accuracy")
    
    print(cal_plt)
  }
}
Warning: Removed 438 rows containing non-finite values (stat_boxplot).
Warning: Removed 216 rows containing non-finite values (stat_boxplot).
Warning: Removed 262 rows containing non-finite values (stat_boxplot).
Warning: Removed 428 rows containing non-finite values (stat_boxplot).
Warning: Removed 380 rows containing non-finite values (stat_boxplot).
Warning: Removed 388 rows containing non-finite values (stat_boxplot).
Warning: Removed 420 rows containing non-finite values (stat_boxplot).
Warning: Removed 230 rows containing non-finite values (stat_boxplot).
Warning: Removed 344 rows containing non-finite values (stat_boxplot).
Warning: Removed 598 rows containing non-finite values (stat_boxplot).
Warning: Removed 486 rows containing non-finite values (stat_boxplot).
Warning: Removed 828 rows containing non-finite values (stat_boxplot).
Warning: Removed 472 rows containing non-finite values (stat_boxplot).
Warning: Removed 772 rows containing non-finite values (stat_boxplot).
Warning: Removed 542 rows containing non-finite values (stat_boxplot).
Warning: Removed 448 rows containing non-finite values (stat_boxplot).
Warning: Removed 220 rows containing non-finite values (stat_boxplot).
Warning: Removed 124 rows containing non-finite values (stat_boxplot).
Warning: Removed 226 rows containing non-finite values (stat_boxplot).
Warning: Removed 170 rows containing non-finite values (stat_boxplot).
Warning: Removed 260 rows containing non-finite values (stat_boxplot).
Warning: Removed 258 rows containing non-finite values (stat_boxplot).
Warning: Removed 332 rows containing non-finite values (stat_boxplot).
Warning: Removed 404 rows containing non-finite values (stat_boxplot).
Warning: Removed 172 rows containing non-finite values (stat_boxplot).
Warning: Removed 42 rows containing non-finite values (stat_boxplot).
Warning: Removed 152 rows containing non-finite values (stat_boxplot).
Warning: Removed 220 rows containing non-finite values (stat_boxplot).
Warning: Removed 386 rows containing non-finite values (stat_boxplot).
Warning: Removed 380 rows containing non-finite values (stat_boxplot).
Warning: Removed 174 rows containing non-finite values (stat_boxplot).
Warning: Removed 360 rows containing non-finite values (stat_boxplot).
Warning: Removed 236 rows containing non-finite values (stat_boxplot).
Warning: Removed 544 rows containing non-finite values (stat_boxplot).
Warning: Removed 516 rows containing non-finite values (stat_boxplot).
Warning: Removed 284 rows containing non-finite values (stat_boxplot).
Warning: Removed 190 rows containing non-finite values (stat_boxplot).
Warning: Removed 498 rows containing non-finite values (stat_boxplot).
Warning: Removed 362 rows containing non-finite values (stat_boxplot).
Warning: Removed 502 rows containing non-finite values (stat_boxplot).
Warning: Removed 610 rows containing non-finite values (stat_boxplot).
Warning: Removed 458 rows containing non-finite values (stat_boxplot).
Warning: Removed 562 rows containing non-finite values (stat_boxplot).
Warning: Removed 482 rows containing non-finite values (stat_boxplot).
Warning: Removed 308 rows containing non-finite values (stat_boxplot).

Even with low bin counts, better calibration can be observed for logreg than for lda.

for (cl1 in 1:(classes - 1))
{
  for (cl2 in (cl1 + 1):classes)
  {
    irrel_plt <- ens_pw_irrel %>% filter(class1 == (cl1 - 1) & class2 == (cl2 - 1)) %>% ggplot() +
      geom_histogram(mapping=aes(x=pred1), bins=30) + facet_grid(cols=vars(train_set), rows=vars(combining_method)) +
      ggtitle(paste("Predictions for irrelevant classes by combiner method ", cl1, " vs ", cl2)) +
      xlab("Predicted probability")
    
    print(irrel_plt)
  }
}

These histograms show, that even for unknown classes, LDA most often produces probabilities close to one and zero. One exception is val_train LDA for class pair 4 and 6, where probabilities are divided evenly. Also the confidence plot for this LDA looks sensibly. Histograms for method logreg, on the other hand produce more evenly distributed probabilities.

CIFAR 10 half

base_dir <- "../data/data_train_val_half_c10"
repls <- 0:0
folds <- 0:49
classes <- 10
combiner_coefs <- load_combiner_coefs(base_dir, repls, folds)
net_pw_results <- read.csv(file.path(base_dir, "net_pw_accuracies.csv"))
ens_pw_results <- read.csv(file.path(base_dir, "ensemble_pw_accuracies.csv"))
net_pw_results[, c("class1", "class2")] <- lapply(net_pw_results[, c("class1", "class2")], as.factor)
ens_pw_results[, c("class1", "class2", "combining_method")] <- lapply(ens_pw_results[, c("class1", "class2", "combining_method")], as.factor)
for (cl1 in 1:(classes - 1))
{
  for (cl2 in (cl1 + 1):classes)
  {
    combiner_plt <- combiner_coefs %>% filter(class1 == cl1 & class2 == cl2) %>% ggplot() + 
      geom_boxplot(aes(x=coefficient, y=value)) +
      facet_grid(cols=vars(train_type), rows=vars(combining_method)) + ggtitle("combiner coefficients")
    
    acc_plt_net <- net_pw_results %>% filter(class1 == (cl1 - 1) & class2 == (cl2 - 1)) %>% ggplot(mapping=aes(x=network, y=accuracy)) + 
      geom_boxplot() + ggtitle("Pairwise accuracies of networks")
    
    acc_plt_ens <- ens_pw_results %>% filter(class1 == (cl1 - 1) & class2 == (cl2 - 1)) %>% ggplot(mapping=aes(y=accuracy)) +
      geom_boxplot() + facet_grid(cols=vars(train_set), rows=vars(combining_method)) + ggtitle("Pairwise accuracies of combiner output") +
      theme(axis.ticks.x=element_blank(), axis.text.x=element_blank())
    
    
    print((acc_plt_net/combiner_plt/acc_plt_ens) + plot_annotation(title=paste("Classes ", cl1, " vs ", cl2)))
  }
}

Val training accuracies have higher variance.

CIFAR 100 half

base_dir <- "../data/data_train_val_half_c100"
repls <- 0:9
folds <- 0:4
classes <- 10
combiner_coefs <- load_combiner_coefs(base_dir, repls, folds)
net_pw_results <- read.csv(file.path(base_dir, "net_pw_accuracies.csv"))
ens_pw_results <- read.csv(file.path(base_dir, "ensemble_pw_accuracies.csv"))
net_pw_results[, c("class1", "class2")] <- lapply(net_pw_results[, c("class1", "class2")], as.factor)
ens_pw_results[, c("class1", "class2", "combining_method")] <- lapply(ens_pw_results[, c("class1", "class2", "combining_method")], as.factor)
for (cl1 in 1:(disp_classes - 1))
{
  for (cl2 in (cl1 + 1):disp_classes)
  {
    combiner_plt <- combiner_coefs %>% filter(class1 == cl1 & class2 == cl2) %>% ggplot() + geom_boxplot(aes(x=coefficient, y=value)) +
      facet_grid(cols=vars(train_type), rows=vars(combining_method)) + ggtitle("Combiner coefficients")
    
    acc_plt_net <- net_pw_results %>% filter(class1 == (cl1 - 1) & class2 == (cl2 - 1)) %>% ggplot(mapping=aes(x=network, y=accuracy)) + 
      geom_boxplot() + ggtitle("Pairwise accuracies of networks")
    
    acc_plt_ens <- ens_pw_results %>% filter(class1 == (cl1 - 1) & class2 == (cl2 - 1)) %>% ggplot(mapping=aes(y=accuracy)) +
      geom_boxplot() + facet_grid(cols=vars(train_set), rows=vars(combining_method)) + 
      ggtitle("Pairwise accuracies of combiner output") +
      theme(axis.ticks.x=element_blank(), axis.text.x=element_blank())
    
    
    print((acc_plt_net/combiner_plt/acc_plt_ens) + plot_annotation(title=paste("Classes ", cl1, " vs ", cl2)))
  }
}

Here are too many class pair combinations to draw them all. TODO: think of some criteria to draw interesting ones.

LS0tDQp0aXRsZTogIlZpc3VhbGl6YXRpb25zIExEQSBjb2VmZmljaWVudHMiDQpvdXRwdXQ6DQogIHBkZl9kb2N1bWVudDogZGVmYXVsdA0KICBodG1sX25vdGVib29rOiBkZWZhdWx0DQotLS0NCg0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoImdncHViciIpDQpsaWJyYXJ5KExEQVRTKQ0KbGlicmFyeShnZ1Zlbm5EaWFncmFtKQ0KbGlicmFyeShzdHJpbmdyKQ0KbGlicmFyeShhYmluZCkNCmxpYnJhcnkocGF0Y2h3b3JrKQ0KDQpzb3VyY2UoInV0aWxzLlIiKQ0KYGBgDQpWaXN1YWxpemF0aW9ucyBvbiB0aHJlZSBzZXRzIG9mIG5ldHdvcmtzLiBGaXJzdCBzZXQgY29uc2lzdHMgb2YgbmV0d29ya3MgdHJhaW5lZCBpbiAzMCByZXBsaWNhdGlvbnMgb24gQ0lGQVIgMTAgdHJhaW5pbmcgc2V0IHdpdGggNTAwIHNhbXBsZSB2YWxpZGF0aW9uIHNldCBleHRyYWN0ZWQgcmFuZG9tbHkgaW4gZWFjaCByZXBsaWNhdGlvbi4gU2Vjb25kIHNldCBjb25zaXN0cyBvZiBuZXR3b3JrcyB0cmFpbmVkIGluIGEgc2luZ2xlIHJlcGxpY2F0aW9uIG9uIGhhbGYgb2YgQ0lGQVIgMTAgdHJhaW5pbmcgc2V0LiBUaGlyZCBzZXQgYXJlIG5ldHdvcmtzIHRyYWluZWQgaW4gMTAgcmVwbGljYXRpb25zIG9uIGhhbGYgb2YgQ0lGQVIgMTAwIHRyYWluaW5nIHNldC4NCg0KDQojIyBDSUZBUiAxMA0KDQpgYGB7cn0NCmJhc2VfZGlyIDwtICIuLi9kYXRhL2RhdGFfdHJhaW5fdmFsX2MxMCINCnJlcGxzIDwtIDA6MjkNCmNsYXNzZXMgPC0gMTANCmNvbWJpbmVyX2NvZWZzIDwtIGxvYWRfY29tYmluZXJfY29lZnMoYmFzZV9kaXIsIHJlcGxzKQ0KbmV0X3B3X3Jlc3VsdHMgPC0gcmVhZC5jc3YoZmlsZS5wYXRoKGJhc2VfZGlyLCAibmV0X3B3X2FjY3VyYWNpZXMuY3N2IikpDQplbnNfcHdfcmVzdWx0cyA8LSByZWFkLmNzdihmaWxlLnBhdGgoYmFzZV9kaXIsICJlbnNlbWJsZV9wd19hY2N1cmFjaWVzLmNzdiIpKQ0KZW5zX3B3X2NhbCA8LSByZWFkLmNzdihmaWxlLnBhdGgoYmFzZV9kaXIsICJlbnNlbWJsZV9wd19jYWxpYnJhdGlvbi5jc3YiKSkNCmVuc19wd19pcnJlbCA8LSByZWFkLmNzdihmaWxlLnBhdGgoYmFzZV9kaXIsICJlbnNlbWJsZV9wd19pcnJlbGV2YW50LmNzdiIpKQ0KbmV0X3B3X3Jlc3VsdHNbLCBjKCJjbGFzczEiLCAiY2xhc3MyIildIDwtIGxhcHBseShuZXRfcHdfcmVzdWx0c1ssIGMoImNsYXNzMSIsICJjbGFzczIiKV0sIGFzLmZhY3RvcikNCmVuc19wd19yZXN1bHRzWywgYygiY2xhc3MxIiwgImNsYXNzMiIsICJjb21iaW5pbmdfbWV0aG9kIildIDwtIGxhcHBseShlbnNfcHdfcmVzdWx0c1ssIGMoImNsYXNzMSIsICJjbGFzczIiLCAiY29tYmluaW5nX21ldGhvZCIpXSwgYXMuZmFjdG9yKQ0KZW5zX3B3X2NhbCRiaW5fYyA8LSAoZW5zX3B3X2NhbCRjb25mX21pbiArIGVuc19wd19jYWwkY29uZl9tYXgpIC8gMg0KZW5zX3B3X2NhbFssIGMoImNsYXNzMSIsICJjbGFzczIiLCAiYmluX2MiLCAiY29tYmluaW5nX21ldGhvZCIpXSA8LSBsYXBwbHkoZW5zX3B3X2NhbFssIGMoImNsYXNzMSIsICJjbGFzczIiLCAiYmluX2MiLCAiY29tYmluaW5nX21ldGhvZCIpXSwgYXMuZmFjdG9yKQ0KZW5zX3B3X2lycmVsWywgYygiY2xhc3MxIiwgImNsYXNzMiIsICJjb21iaW5pbmdfbWV0aG9kIildIDwtIGxhcHBseShlbnNfcHdfaXJyZWxbLCBjKCJjbGFzczEiLCAiY2xhc3MyIiwgImNvbWJpbmluZ19tZXRob2QiKV0sIGFzLmZhY3RvcikNCg0KYGBgDQoNCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTZ9DQpmb3IgKGNsMSBpbiAxOihjbGFzc2VzIC0gMSkpDQp7DQogIGZvciAoY2wyIGluIChjbDEgKyAxKTpjbGFzc2VzKQ0KICB7DQogICAgY29tYmluZXJfcGx0IDwtIGNvbWJpbmVyX2NvZWZzICU+JSBmaWx0ZXIoY2xhc3MxID09IGNsMSAmIGNsYXNzMiA9PSBjbDIpICU+JSBnZ3Bsb3QoKSArIGdlb21fYm94cGxvdChhZXMoeD1jb2VmZmljaWVudCwgeT12YWx1ZSkpICsNCiAgICAgIGZhY2V0X2dyaWQoY29scz12YXJzKHRyYWluX3R5cGUpLCByb3dzPXZhcnMoY29tYmluaW5nX21ldGhvZCkpICsgZ2d0aXRsZSgiY29tYmluZXIgY29lZmZpY2llbnRzIikNCiAgICANCiAgICBhY2NfcGx0X25ldCA8LSBuZXRfcHdfcmVzdWx0cyAlPiUgZmlsdGVyKGNsYXNzMSA9PSAoY2wxIC0gMSkgJiBjbGFzczIgPT0gKGNsMiAtIDEpKSAlPiUgZ2dwbG90KG1hcHBpbmc9YWVzKHg9bmV0d29yaywgeT1hY2N1cmFjeSkpICsgDQogICAgICBnZW9tX2JveHBsb3QoKSArIGdndGl0bGUoIlBhaXJ3aXNlIGFjY3VyYWNpZXMgb2YgbmV0d29ya3MiKQ0KICAgIA0KICAgIGFjY19wbHRfZW5zIDwtIGVuc19wd19yZXN1bHRzICU+JSBmaWx0ZXIoY2xhc3MxID09IChjbDEgLSAxKSAmIGNsYXNzMiA9PSAoY2wyIC0gMSkpICU+JSBnZ3Bsb3QobWFwcGluZz1hZXMoeT1hY2N1cmFjeSkpICsNCiAgICAgIGdlb21fYm94cGxvdCgpICsgZmFjZXRfZ3JpZChjb2xzPXZhcnModHJhaW5fc2V0KSwgcm93cz12YXJzKGNvbWJpbmluZ19tZXRob2QpKSArIGdndGl0bGUoIlBhaXJ3aXNlIGFjY3VyYWNpZXMgb2YgY29tYmluZXIgb3V0cHV0IikgKw0KICAgICAgdGhlbWUoYXhpcy50aWNrcy54PWVsZW1lbnRfYmxhbmsoKSwgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpKQ0KICAgIA0KICAgIA0KICAgIHByaW50KChhY2NfcGx0X25ldC9jb21iaW5lcl9wbHQvYWNjX3BsdF9lbnMpICsgcGxvdF9hbm5vdGF0aW9uKHRpdGxlPXBhc3RlKCJDbGFzc2VzICIsIGNsMSwgIiB2cyAiLCBjbDIpKSkNCiAgfQ0KfQ0KYGBgDQpWYWwgdHJhaW5pbmcgYWNjdXJhY2llcyBoYXZlIGhpZ2hlciB2YXJpYW5jZS4NCg0KDQpBbiBhdHRlbXB0IHRvIHZpc3VhbGl6ZSBjYWxpYnJhdGlvbiBvZiBMREFzLiBBbG1vc3QgYWxsIGVsZW1lbnRzIGFyZSBwcmVzZW50IG5lYXIgdGhlIHplcm8gYW5kIG9uZSBwcm9iYWJpbGl0aWVzLCB0aGVyZWZvcmUgdGhlIHZpc3VhbGl6YXRpb24gaXMgbm90IHZlcnkgaW5mb3JtYXRpdmUuDQpgYGB7cn0NCmZvciAoY2wxIGluIDE6KGNsYXNzZXMgLSAxKSkNCnsNCiAgZm9yIChjbDIgaW4gKGNsMSArIDEpOmNsYXNzZXMpDQogIHsNCiAgICBjYWxfY291bnRfcGx0IDwtIGVuc19wd19jYWwgJT4lIGZpbHRlcihjbGFzczEgPT0gKGNsMSAtIDEpICYgY2xhc3MyID09IChjbDIgLSAxKSkgJT4lIGdncGxvdCgpICsNCiAgICAgIGdlb21fYm94cGxvdChtYXBwaW5nPWFlcyh4PWJpbl9jLCB5PWJpbl9jb3VudCkpICsgZmFjZXRfZ3JpZChjb2xzPXZhcnModHJhaW5fc2V0KSwgcm93cz12YXJzKGNvbWJpbmluZ19tZXRob2QpKSArDQogICAgICBnZ3RpdGxlKHBhc3RlKCJDbGFzc2VzICIsIGNsMSwgIiB2cyAiLCBjbDIpKSArIHhsYWIoIkNvbmZpZGVuY2UiKSArIHlsYWIoIkNvdW50IikNCiAgICANCiAgICBwcmludChjYWxfY291bnRfcGx0KQ0KICB9DQp9DQpgYGANCg0KDQpgYGB7cn0NCmZvciAoY2wxIGluIDE6KGNsYXNzZXMgLSAxKSkNCnsNCiAgZm9yIChjbDIgaW4gKGNsMSArIDEpOmNsYXNzZXMpDQogIHsNCiAgICBjYWxfcGx0IDwtIGVuc19wd19jYWwgJT4lIGZpbHRlcihjbGFzczEgPT0gKGNsMSAtIDEpICYgY2xhc3MyID09IChjbDIgLSAxKSkgJT4lIGdncGxvdCgpICsNCiAgICAgIGdlb21fYm94cGxvdChtYXBwaW5nPWFlcyh4PWJpbl9jLCB5PWJpbl9hY2N1cmFjeSkpICsgZmFjZXRfZ3JpZChjb2xzPXZhcnModHJhaW5fc2V0KSwgcm93cz12YXJzKGNvbWJpbmluZ19tZXRob2QpKSArDQogICAgICBnZ3RpdGxlKHBhc3RlKCJDbGFzc2VzICIsIGNsMSwgIiB2cyAiLCBjbDIpKSArIHhsYWIoIkNvbmZpZGVuY2UiKSArIHlsYWIoIkFjY3VyYWN5IikNCiAgICANCiAgICBwcmludChjYWxfcGx0KQ0KICB9DQp9DQpgYGANCkV2ZW4gd2l0aCBsb3cgYmluIGNvdW50cywgYmV0dGVyIGNhbGlicmF0aW9uIGNhbiBiZSBvYnNlcnZlZCBmb3IgbG9ncmVnIHRoYW4gZm9yIGxkYS4NCg0KYGBge3J9DQpmb3IgKGNsMSBpbiAxOihjbGFzc2VzIC0gMSkpDQp7DQogIGZvciAoY2wyIGluIChjbDEgKyAxKTpjbGFzc2VzKQ0KICB7DQogICAgaXJyZWxfcGx0IDwtIGVuc19wd19pcnJlbCAlPiUgZmlsdGVyKGNsYXNzMSA9PSAoY2wxIC0gMSkgJiBjbGFzczIgPT0gKGNsMiAtIDEpKSAlPiUgZ2dwbG90KCkgKw0KICAgICAgZ2VvbV9oaXN0b2dyYW0obWFwcGluZz1hZXMoeD1wcmVkMSksIGJpbnM9MzApICsgZmFjZXRfZ3JpZChjb2xzPXZhcnModHJhaW5fc2V0KSwgcm93cz12YXJzKGNvbWJpbmluZ19tZXRob2QpKSArDQogICAgICBnZ3RpdGxlKHBhc3RlKCJQcmVkaWN0aW9ucyBmb3IgaXJyZWxldmFudCBjbGFzc2VzIGJ5IGNvbWJpbmVyIG1ldGhvZCAiLCBjbDEsICIgdnMgIiwgY2wyKSkgKw0KICAgICAgeGxhYigiUHJlZGljdGVkIHByb2JhYmlsaXR5IikNCiAgICANCiAgICBwcmludChpcnJlbF9wbHQpDQogIH0NCn0NCmBgYA0KVGhlc2UgaGlzdG9ncmFtcyBzaG93LCB0aGF0IGV2ZW4gZm9yIHVua25vd24gY2xhc3NlcywgTERBIG1vc3Qgb2Z0ZW4gcHJvZHVjZXMgcHJvYmFiaWxpdGllcyBjbG9zZSB0byBvbmUgYW5kIHplcm8uIE9uZSBleGNlcHRpb24gaXMgdmFsX3RyYWluIExEQSBmb3IgY2xhc3MgcGFpciA0IGFuZCA2LCB3aGVyZSBwcm9iYWJpbGl0aWVzIGFyZSBkaXZpZGVkIGV2ZW5seS4gQWxzbyB0aGUgY29uZmlkZW5jZSBwbG90IGZvciB0aGlzIExEQSBsb29rcyBzZW5zaWJseS4NCkhpc3RvZ3JhbXMgZm9yIG1ldGhvZCBsb2dyZWcsIG9uIHRoZSBvdGhlciBoYW5kIHByb2R1Y2UgbW9yZSBldmVubHkgZGlzdHJpYnV0ZWQgcHJvYmFiaWxpdGllcy4NCg0KDQojIyBDSUZBUiAxMCBoYWxmDQoNCmBgYHtyfQ0KYmFzZV9kaXIgPC0gIi4uL2RhdGEvZGF0YV90cmFpbl92YWxfaGFsZl9jMTAiDQpyZXBscyA8LSAwOjANCmZvbGRzIDwtIDA6NDkNCmNsYXNzZXMgPC0gMTANCmNvbWJpbmVyX2NvZWZzIDwtIGxvYWRfY29tYmluZXJfY29lZnMoYmFzZV9kaXIsIHJlcGxzLCBmb2xkcykNCm5ldF9wd19yZXN1bHRzIDwtIHJlYWQuY3N2KGZpbGUucGF0aChiYXNlX2RpciwgIm5ldF9wd19hY2N1cmFjaWVzLmNzdiIpKQ0KZW5zX3B3X3Jlc3VsdHMgPC0gcmVhZC5jc3YoZmlsZS5wYXRoKGJhc2VfZGlyLCAiZW5zZW1ibGVfcHdfYWNjdXJhY2llcy5jc3YiKSkNCm5ldF9wd19yZXN1bHRzWywgYygiY2xhc3MxIiwgImNsYXNzMiIpXSA8LSBsYXBwbHkobmV0X3B3X3Jlc3VsdHNbLCBjKCJjbGFzczEiLCAiY2xhc3MyIildLCBhcy5mYWN0b3IpDQplbnNfcHdfcmVzdWx0c1ssIGMoImNsYXNzMSIsICJjbGFzczIiLCAiY29tYmluaW5nX21ldGhvZCIpXSA8LSBsYXBwbHkoZW5zX3B3X3Jlc3VsdHNbLCBjKCJjbGFzczEiLCAiY2xhc3MyIiwgImNvbWJpbmluZ19tZXRob2QiKV0sIGFzLmZhY3RvcikNCmBgYA0KDQpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTE2fQ0KZm9yIChjbDEgaW4gMTooY2xhc3NlcyAtIDEpKQ0Kew0KICBmb3IgKGNsMiBpbiAoY2wxICsgMSk6Y2xhc3NlcykNCiAgew0KICAgIGNvbWJpbmVyX3BsdCA8LSBjb21iaW5lcl9jb2VmcyAlPiUgZmlsdGVyKGNsYXNzMSA9PSBjbDEgJiBjbGFzczIgPT0gY2wyKSAlPiUgZ2dwbG90KCkgKyANCiAgICAgIGdlb21fYm94cGxvdChhZXMoeD1jb2VmZmljaWVudCwgeT12YWx1ZSkpICsNCiAgICAgIGZhY2V0X2dyaWQoY29scz12YXJzKHRyYWluX3R5cGUpLCByb3dzPXZhcnMoY29tYmluaW5nX21ldGhvZCkpICsgZ2d0aXRsZSgiY29tYmluZXIgY29lZmZpY2llbnRzIikNCiAgICANCiAgICBhY2NfcGx0X25ldCA8LSBuZXRfcHdfcmVzdWx0cyAlPiUgZmlsdGVyKGNsYXNzMSA9PSAoY2wxIC0gMSkgJiBjbGFzczIgPT0gKGNsMiAtIDEpKSAlPiUgZ2dwbG90KG1hcHBpbmc9YWVzKHg9bmV0d29yaywgeT1hY2N1cmFjeSkpICsgDQogICAgICBnZW9tX2JveHBsb3QoKSArIGdndGl0bGUoIlBhaXJ3aXNlIGFjY3VyYWNpZXMgb2YgbmV0d29ya3MiKQ0KICAgIA0KICAgIGFjY19wbHRfZW5zIDwtIGVuc19wd19yZXN1bHRzICU+JSBmaWx0ZXIoY2xhc3MxID09IChjbDEgLSAxKSAmIGNsYXNzMiA9PSAoY2wyIC0gMSkpICU+JSBnZ3Bsb3QobWFwcGluZz1hZXMoeT1hY2N1cmFjeSkpICsNCiAgICAgIGdlb21fYm94cGxvdCgpICsgZmFjZXRfZ3JpZChjb2xzPXZhcnModHJhaW5fc2V0KSwgcm93cz12YXJzKGNvbWJpbmluZ19tZXRob2QpKSArIGdndGl0bGUoIlBhaXJ3aXNlIGFjY3VyYWNpZXMgb2YgY29tYmluZXIgb3V0cHV0IikgKw0KICAgICAgdGhlbWUoYXhpcy50aWNrcy54PWVsZW1lbnRfYmxhbmsoKSwgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpKQ0KICAgIA0KICAgIA0KICAgIHByaW50KChhY2NfcGx0X25ldC9jb21iaW5lcl9wbHQvYWNjX3BsdF9lbnMpICsgcGxvdF9hbm5vdGF0aW9uKHRpdGxlPXBhc3RlKCJDbGFzc2VzICIsIGNsMSwgIiB2cyAiLCBjbDIpKSkNCiAgfQ0KfQ0KYGBgDQpWYWwgdHJhaW5pbmcgYWNjdXJhY2llcyBoYXZlIGhpZ2hlciB2YXJpYW5jZS4NCg0KIyMgQ0lGQVIgMTAwIGhhbGYNCg0KYGBge3J9DQpiYXNlX2RpciA8LSAiLi4vZGF0YS9kYXRhX3RyYWluX3ZhbF9oYWxmX2MxMDAiDQpyZXBscyA8LSAwOjkNCmZvbGRzIDwtIDA6NA0KZGlzcF9jbGFzc2VzIDwtIDEwDQpjb21iaW5lcl9jb2VmcyA8LSBsb2FkX2NvbWJpbmVyX2NvZWZzKGJhc2VfZGlyLCByZXBscywgZm9sZHMpDQpuZXRfcHdfcmVzdWx0cyA8LSByZWFkLmNzdihmaWxlLnBhdGgoYmFzZV9kaXIsICJuZXRfcHdfYWNjdXJhY2llcy5jc3YiKSkNCmVuc19wd19yZXN1bHRzIDwtIHJlYWQuY3N2KGZpbGUucGF0aChiYXNlX2RpciwgImVuc2VtYmxlX3B3X2FjY3VyYWNpZXMuY3N2IikpDQpuZXRfcHdfcmVzdWx0c1ssIGMoImNsYXNzMSIsICJjbGFzczIiKV0gPC0gbGFwcGx5KG5ldF9wd19yZXN1bHRzWywgYygiY2xhc3MxIiwgImNsYXNzMiIpXSwgYXMuZmFjdG9yKQ0KZW5zX3B3X3Jlc3VsdHNbLCBjKCJjbGFzczEiLCAiY2xhc3MyIiwgImNvbWJpbmluZ19tZXRob2QiKV0gPC0gbGFwcGx5KGVuc19wd19yZXN1bHRzWywgYygiY2xhc3MxIiwgImNsYXNzMiIsICJjb21iaW5pbmdfbWV0aG9kIildLCBhcy5mYWN0b3IpDQpgYGANCg0KYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xNn0NCmZvciAoY2wxIGluIDE6KGRpc3BfY2xhc3NlcyAtIDEpKQ0Kew0KICBmb3IgKGNsMiBpbiAoY2wxICsgMSk6ZGlzcF9jbGFzc2VzKQ0KICB7DQogICAgY29tYmluZXJfcGx0IDwtIGNvbWJpbmVyX2NvZWZzICU+JSBmaWx0ZXIoY2xhc3MxID09IGNsMSAmIGNsYXNzMiA9PSBjbDIpICU+JSBnZ3Bsb3QoKSArIGdlb21fYm94cGxvdChhZXMoeD1jb2VmZmljaWVudCwgeT12YWx1ZSkpICsNCiAgICAgIGZhY2V0X2dyaWQoY29scz12YXJzKHRyYWluX3R5cGUpLCByb3dzPXZhcnMoY29tYmluaW5nX21ldGhvZCkpICsgZ2d0aXRsZSgiQ29tYmluZXIgY29lZmZpY2llbnRzIikNCiAgICANCiAgICBhY2NfcGx0X25ldCA8LSBuZXRfcHdfcmVzdWx0cyAlPiUgZmlsdGVyKGNsYXNzMSA9PSAoY2wxIC0gMSkgJiBjbGFzczIgPT0gKGNsMiAtIDEpKSAlPiUgZ2dwbG90KG1hcHBpbmc9YWVzKHg9bmV0d29yaywgeT1hY2N1cmFjeSkpICsgDQogICAgICBnZW9tX2JveHBsb3QoKSArIGdndGl0bGUoIlBhaXJ3aXNlIGFjY3VyYWNpZXMgb2YgbmV0d29ya3MiKQ0KICAgIA0KICAgIGFjY19wbHRfZW5zIDwtIGVuc19wd19yZXN1bHRzICU+JSBmaWx0ZXIoY2xhc3MxID09IChjbDEgLSAxKSAmIGNsYXNzMiA9PSAoY2wyIC0gMSkpICU+JSBnZ3Bsb3QobWFwcGluZz1hZXMoeT1hY2N1cmFjeSkpICsNCiAgICAgIGdlb21fYm94cGxvdCgpICsgZmFjZXRfZ3JpZChjb2xzPXZhcnModHJhaW5fc2V0KSwgcm93cz12YXJzKGNvbWJpbmluZ19tZXRob2QpKSArIA0KICAgICAgZ2d0aXRsZSgiUGFpcndpc2UgYWNjdXJhY2llcyBvZiBjb21iaW5lciBvdXRwdXQiKSArDQogICAgICB0aGVtZShheGlzLnRpY2tzLng9ZWxlbWVudF9ibGFuaygpLCBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCkpDQogICAgDQogICAgDQogICAgcHJpbnQoKGFjY19wbHRfbmV0L2NvbWJpbmVyX3BsdC9hY2NfcGx0X2VucykgKyBwbG90X2Fubm90YXRpb24odGl0bGU9cGFzdGUoIkNsYXNzZXMgIiwgY2wxLCAiIHZzICIsIGNsMikpKQ0KICB9DQp9DQpgYGANCg0KSGVyZSBhcmUgdG9vIG1hbnkgY2xhc3MgcGFpciBjb21iaW5hdGlvbnMgdG8gZHJhdyB0aGVtIGFsbC4gVE9ETzogdGhpbmsgb2Ygc29tZSBjcml0ZXJpYSB0byBkcmF3IGludGVyZXN0aW5nIG9uZXMuIA0K